﻿// Project: Salient
// http://salient.codeplex.com
// 
// Copyright 2010, Sky Sanders <sky at skysanders.net>
// Dual licensed under the MIT or GPL Version 2 licenses.
// http://salient.codeplex.com/license
//  
// Date: June 16 2010 
using System;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Threading;

namespace Salient.Security
{
    /// <summary>
    /// TODO: implement secure password mechanism
    /// </summary>
    /// <remarks>
    /// source: http://www.deploymentcentric.com/Blogs/joe/PermaLink,guid,78e50c9e-b873-46a1-b435-c64765135716.aspx
    /// </remarks>
    public class IdentityBlock : IDisposable
    {
        private readonly WindowsImpersonationContext _wic;

        ///<summary>
        ///</summary>
        ///<param name="user"></param>
        public IdentityBlock(string user)
        {
            SetThreadIdentity(user);
        }

        ///<summary>
        ///</summary>
        ///<param name="user"></param>
        ///<param name="impersonateMe"></param>
        public IdentityBlock(string user, bool impersonateMe)
        {
            if (impersonateMe)
            {
                _wic = ImpersonateUser(user);
            }
            else
            {
                SetThreadIdentity(user);
            }
        }

        #region IDisposable Members

        /// <summary>
        /// Dispose of this object's resources.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(true); // as a service to those who might inherit from us
        }

        #endregion

        ///<summary>
        ///</summary>
        ///<param name="user"></param>
        ///<returns></returns>
        ///<exception cref="NotImplementedException"></exception>
        public string GetPassword(string user)
        {
            throw new NotImplementedException();
        }

        [DllImport("advapi32.dll", SetLastError = true)]
        private static extern bool LogonUser(
            string principal,
            string authority,
            string password,
            LogonTypes logonType,
            LogonProviders logonProvider,
            out IntPtr token);

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern bool CloseHandle(IntPtr handle);

        /// <summary>
        /// Allows an <see cref="T:System.Object"/> to attempt to free resources and perform other cleanup operations before the <see cref="T:System.Object"/> is reclaimed by garbage collection.
        /// </summary>
        ~IdentityBlock()
        {
            Dispose(false);
        }

        /// <summary>
        /// Set the thread identity
        /// </summary>
        /// <param name="user"></param>
        private void SetThreadIdentity(string user)
        {
            string pwd = GetPassword(user); // psuedo code
            IntPtr token;
            bool result = LogonUser(
                user, "[Put_Your_Domain_Here]",
                pwd,
                LogonTypes.Interactive,
                LogonProviders.Default,
                out token);

            if (result)
            {
                WindowsIdentity id = new WindowsIdentity(token);
                CloseHandle(token);
                WindowsPrincipal p = new WindowsPrincipal(id);
                Thread.CurrentPrincipal = p;
            }
            else
            {
                Console.WriteLine("LogonUser failed: {0}",
                                  Marshal.GetLastWin32Error());
                throw new Exception(string.Format("LogonUser failed: {0}. User: {1}",
                                                  Marshal.GetLastWin32Error(), user));
            }
        }

        /// <summary>
        /// Set the thread identity and impersonate.
        /// </summary>
        private WindowsImpersonationContext ImpersonateUser(string user)
        {
            WindowsIdentity id;

            string pwd = GetPassword(user); // psuedo code
            IntPtr token;
            bool result = LogonUser(
                user,
                "[Put_Your_Domain_Here]",
                pwd,
                LogonTypes.Interactive,
                LogonProviders.Default,
                out token);
            if (result)
            {
                id = new WindowsIdentity(token);
                CloseHandle(token);
                WindowsPrincipal p = new WindowsPrincipal(id);
                Thread.CurrentPrincipal = p;
                return id.Impersonate();
            }
            Console.WriteLine("LogonUser failed: {0}",
                              Marshal.GetLastWin32Error());
            throw new Exception(string.Format("LogonUser failed: {0}. User: {1}",
                                              Marshal.GetLastWin32Error(), user));
        }

        /// <summary>
        /// Free the instance variables of this object.
        /// </summary>
        protected virtual void Dispose(bool disposing)
        {
            if (!disposing)
                return; // we're being collected, so let the GC take care of this object
            if (_wic != null) _wic.Undo();
            WindowsPrincipal p = new WindowsPrincipal(WindowsIdentity.GetCurrent());
            Thread.CurrentPrincipal = p;
        }

        #region Nested type: LogonProviders

        private enum LogonProviders : uint
        {
            Default = 0, // default for platform (use this!)
            WinNT35, // sends smoke signals to authority
            WinNT40, // uses NTLM
            WinNT50 // negotiates Kerb or NTLM
        }

        #endregion

        #region Nested type: LogonTypes

        private enum LogonTypes : uint
        {
            Interactive = 2,
            Network,
            Batch,
            Service,
            NetworkCleartext = 8,
            NewCredentials
        }

        #endregion
    }
}